package com.gopay.common.cipher.utils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;

import com.gopay.common.cipher.algorithm.CipherAlgorithmContext;
import com.gopay.common.cipher.algorithm.baseAlgorithm.Cipher3DES;
import com.gopay.common.cipher.algorithm.baseAlgorithm.Encrypt;
import com.gopay.common.cipher.constants.EncryptType;
import com.gopay.common.cipher.constants.SensitiveInfoConstants;
import com.gopay.common.cipher.vo.SecurityInfoKeyBox;
import com.gopay.common.cipher.vo.SecurityKeyInfo;


/**
 * 
 * @ClassName: SensitiveInfoUtils 
 * @Description: 敏感信息加解密工具类 
 * @author zyt
 * @date 2016年9月9日 下午5:44:57
 */
public class SensitiveInfoUtils {

	private static Logger logger = LoggerFactory.getLogger(SensitiveInfoUtils.class);

	/**
	 * 
	 * @Description: 	获取密钥box ，先从redis中获取，如果获取失败，从本地的备份securityInfoKeyBox
	 * 					中获取，否则，从本地的bean备份中获取。
	 * @author zyt
	 */
	private static SecurityInfoKeyBox getKeyBox(){
		//modified by zyt , 2017-7-13，获取当前生效的keyBox，增加redis校验等机制
		return DataKeyUtils.getValidKeyBox();
	}
	/**
	 * 
	 * @Description: 	返回历史所有密钥加密后的密文list 
	 * @param content	明文
	 * @return 			密文list
	 * @author 			zyt
	 */
	public static List<String> getAllHisCipherText(String content){
		List<String> cipherTextList= new ArrayList<String>();

		//对入参做非空流程
		if(StringUtils.isBlank(content)){
			return cipherTextList;
		}
		//添加明文
		cipherTextList.add(content);
		
		//添加DES加密的密文
		try {
			Encrypt encrypt = new Encrypt();
			String desEncryptContent = SensitiveInfoConstants.ENCRYPT_MARK_DES + encrypt.encrypt(content);
			cipherTextList.add(desEncryptContent);
		} catch (Exception e) {
			logger.error("查询中单des加密失败，查询可能有误", e);
		}
		
		//添加3des 2key加密的密文
		byte[] twoDesEncryptByte;
		try {
			twoDesEncryptByte = Cipher3DES.encryptBase64(SensitiveInfoConstants.key3des2keyBytes, content.getBytes("utf-8"));
			String twoDesEncryptContent = SensitiveInfoConstants.ENCRYPT_MARK_3DES_2KEY + new String(twoDesEncryptByte);
			cipherTextList.add(twoDesEncryptContent);
		} catch (Exception e) {
			logger.error("查询中3des 2key加密失败，查询可能有误：", e);
		}
		
		//添加3des 3key所有密钥加密的密文
		Map<String, SecurityKeyInfo> allKeyInfos = getKeyBox().getAllKeyInfos();
		
		Iterator<Entry<String, SecurityKeyInfo>> iterator = allKeyInfos.entrySet().iterator();
		while(iterator.hasNext()){
			Entry entry = iterator.next();
			SecurityKeyInfo keyInfo = (SecurityKeyInfo)entry.getValue();
			try {
				cipherTextList.add(CipherAlgorithmContext.encryptByKeyInfo(content, keyInfo));
			} catch (Exception e) {
				logger.error("查询中3des 3key加密失败，查询可能有误：", e);
			}
		}
		
		return cipherTextList;
	}

	/**
	 *
	 * @Description: 	返回历史所有密钥加密后的密文Array
	 * @param content	明文
	 * @return 			密文集合
	 * @author 			zyt
	 */
	public static Object[] getAllHisCipherArray(String content){
		if(StringUtils.isBlank(content)){
			return null;
		}
		List<String> cipherTextList= new ArrayList<String>();

		//对入参做非空流程
		if(StringUtils.isBlank(content)){
			return cipherTextList.toArray();
		}
		//添加明文
		cipherTextList.add(content);

		//添加DES加密的密文
		try {
			Encrypt encrypt = new Encrypt();
			String desEncryptContent = SensitiveInfoConstants.ENCRYPT_MARK_DES + encrypt.encrypt(content);
			cipherTextList.add(desEncryptContent);
		} catch (Exception e) {
			logger.error("查询中单des加密失败，查询可能有误", e);
		}

		//添加3des 2key加密的密文
		byte[] twoDesEncryptByte;
		try {
			twoDesEncryptByte = Cipher3DES.encryptBase64(SensitiveInfoConstants.key3des2keyBytes, content.getBytes("utf-8"));
			String twoDesEncryptContent = SensitiveInfoConstants.ENCRYPT_MARK_3DES_2KEY + new String(twoDesEncryptByte);
			cipherTextList.add(twoDesEncryptContent);
		} catch (Exception e) {
			logger.error("查询中3des 2key加密失败，查询可能有误：", e);
		}

		//添加3des 3key所有密钥加密的密文
		Map<String, SecurityKeyInfo> allKeyInfos = getKeyBox().getAllKeyInfos();

		Iterator<Entry<String, SecurityKeyInfo>> iterator = allKeyInfos.entrySet().iterator();
		while(iterator.hasNext()){
			Entry entry = iterator.next();
			SecurityKeyInfo keyInfo = (SecurityKeyInfo)entry.getValue();
			try {
				cipherTextList.add(CipherAlgorithmContext.encryptByKeyInfo(content, keyInfo));
			} catch (Exception e) {
				logger.error("查询中3des 3key加密失败，查询可能有误：", e);
			}
		}

		return cipherTextList.toArray();
	}

	/**
	 * 
	 * @Description: 	校验传入字符串是否已经加密：
	 * 					如果已经加密，则不做任何操作，直接返回
	 * 					如果未加密，使用当前的加密算法及密钥进行加密，返回密文 
	 * 					如果是hibernate自动调用，则不做加密操作
	 * @param content	需要判断并机密的字符串
	 * @return 			如果需要加密，则返回加密后的字符串，否则，返回原字符串
	 * @author zyt
	 */
	public static String smartEncryptWithCheck(String content){
		if(StringUtils.isBlank(content)){
			return content;
		}
		if(isNeedShield()){
			return content;
		}
		if (StringUtils.contains(content, SensitiveInfoConstants.ENCRYPT_COMMON_MARK)
				|| StringUtils.startsWith(content, SensitiveInfoConstants.ENCRYPT_MARK_DES)) {
			//若已经是密文，但是密钥不是当前密钥，则解密重新加密
			EncryptType encryptType = CipherAlgorithmContext.getEncryptType(content);
			if(encryptType != getKeyBox().getCurrentKeyInfo().getEncryptType()){
				String orginContent = smartDecrypt(content);
				String cipherContent = smartEncrypt(orginContent);

				return cipherContent;
			} else {
				return content;
			}
		} else {
			String cipherContent = smartEncrypt(content);
			return cipherContent;
		}
	}
	
	
	/**
	 * 
	 * @Description: 		判断是否应该解密，并对传入字符串进行解密操作：
	 * 						如果是hibernate在插入等操作的拼接sql阶段调用的解密，则不去做解密，返回原文
	 * 						如果是其他地方的嗲用（如 dao等），则对传入的字符串进行解密操作并返回
	 * @param ciphertext	需要判断并解密的字符串
	 * @return 				如果需要解密，则返回解密后的字符串，否则，返回原字符串
	 * @author zyt
	 */
	public static String smartDecryptWithCheck(String ciphertext){
		if(StringUtils.isBlank(ciphertext)){
			return ciphertext;
		}
		if(isNeedShield()){
			return ciphertext;
		}
		/**
		 * 注释while外的解密代码，解决解密异常时候，发送两次错误短信的bug
		 * 都放到while中处理，可以对多次加密的密文成功解密
		 */
		//String text = smartDecrypt(ciphertext);
		String text = ciphertext;
    	while(text.indexOf(SensitiveInfoConstants.ENCRYPT_COMMON_MARK) >= 0 || 
    			text.indexOf(SensitiveInfoConstants.ENCRYPT_MARK_DES) >= 0){
    		//modified by zyt,2017-7-10，增加临时变量，每次对解密返回的值判断是否与解密前一致，若是，解密失败，返回之前的密文
			// 解决多重加密情况下，解密失败后，循环解密问题
    		String tmpText = text;
    		text = smartDecrypt(text);
    		if(StringUtils.equals(tmpText, text)){
    			return ciphertext;
    		}
    	}
    	return text;
	}

	/**
	 * 是否需要屏蔽加解密（即返回原文）
	 * 1 若hibernate底层调用。根据AbstractEntityTuplizer类名校验，则需要屏蔽。否则在hibernate最后持久化到数据库，拼接sql的
	 * 		时候，调用get返回明文，持久化加密将失效
	 * @return
	 */
	private static boolean isNeedShield(){
		StackTraceElement[] stackElements = Thread.currentThread().getStackTrace();
		if (stackElements != null) {
			for (StackTraceElement ste : stackElements) {
				if(ste.getClassName().contains("AbstractEntityTuplizer") ||
						ste.getClassName().contains("ibatis.reflection" )){
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * 
	 * @Description: 			对加密内容做自动修改， 判断加密方式与当前密钥是否一致，或者密钥版本与当前使用的是否一致，如果一致则返回原文，如果不一致，则解密后使用新密钥加密返回 
	 * @param cipherContent		原密文
	 * @return 					处理后的内容
	 * @author zyt
	 */
	public static String autoReencrypt(String cipherContent){
		String resContent = cipherContent;
		if(StringUtils.isNotBlank(cipherContent) && isNeedUpdate(cipherContent)){
			/**
			 * 此处不调用smartDecryptWithCheck方法，防止DetachedCriteria等调用的时候，屏蔽掉解密步骤
			 * 导致对密文进行加密的多重加密错误
			 */
			//resContent = smartDecryptWithCheck(cipherContent);
			while(resContent.indexOf(SensitiveInfoConstants.ENCRYPT_COMMON_MARK) >= 0 ||
					resContent.indexOf(SensitiveInfoConstants.ENCRYPT_MARK_DES) >= 0){
				resContent = smartDecrypt(resContent);
				if(StringUtils.equals(cipherContent, resContent)){
					//modified by zyt,2017-7-10，解密失败直接返回，修改break导致继续走到外面代码的加密，引起的多重加密问题
					return cipherContent;
				}
			}
			/**
			 * DetachedCriteria的查询，会在hibernate底层调用与hibernate最后插入（修改）数据一样的方法（AbstractEntityTuplizer）
			 * 导致使用DetachedCriteria的查询，虽然走实体类，但是在smartEncryptWithCheck中会拦住，不做加密，返回原文。
			 * 而上面已经调用smartDecryptWithCheck方法，可以确保出来的是明文，此处直接调用smartEncrypt方法即可。
			 */
			//resContent = smartEncryptWithCheck(resContent);
			resContent = smartEncrypt(resContent);
		}
		
		return resContent;
	}
	
	/**
	 * 
	 * @Description: 		是否需要自动更新内容，判断加密方式与当前密钥是否一致，或者密钥版本与当前使用的是否一致，
	 * @param cipherContent	加密内容
	 * @return 				一致：false，不一致：false
	 * @author zyt
	 */
	private static boolean isNeedUpdate(String cipherContent){
		EncryptType encryptType = CipherAlgorithmContext.getEncryptType(cipherContent);
		SecurityKeyInfo currentDataKeyInfo = getCurrentDataKeyInfo();
		if(currentDataKeyInfo == null || currentDataKeyInfo.getEncryptType() == null || StringUtils.isBlank(currentDataKeyInfo.getKeyVersion())){
			return false;
		}
		if(encryptType == null || currentDataKeyInfo.getEncryptType() != encryptType){
			return true;
		} else {
			String thisKeyVersion = getKeyVersionByCipherText(cipherContent, encryptType);
			if(!StringUtils.equals(thisKeyVersion, currentDataKeyInfo.getKeyVersion())){
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * 
	 * @Description: 		智能加密，获取正在使用的密钥，以及加密方式，为明文进行加密 
	 * 						若加密失败（密钥不存在等原因），返回明文
	 * @param content		明文
	 * @return 				密文
	 * @author zyt
	 */
	public static String smartEncrypt(String content){
		if(StringUtils.isBlank(content)){
			return content;
		}
		SecurityKeyInfo keyInfo = null;
		try {
			keyInfo = getCurrentDataKeyInfo();
		} catch (Exception e) {
			logger.error("获取当前密钥失败：" , e);
		}
		if(keyInfo != null && StringUtils.isNotBlank(keyInfo.getKeyContent())){
			try {
				String cipherContent = CipherAlgorithmContext.encryptByKeyInfo(content, keyInfo);
				/**
				 * 将加密的密文与明文作为字典存储到字典表中，初次上线应急方案，后续将删除此行代码。
				 */
				//delte by zyt， 2017-7-14，注释掉应急代码（存储字典表）
				//SensitiveInfoDictionaryUtils.sendCipherContextAndContext(cipherContent, content);

				return cipherContent;
			} catch (Exception e) {
				handleException(e);
				logger.error("加密失败，返回原文，原因：", e);
				return content;
			}
		} else {
			logger.error("当前使用密钥为空，加密失败，返回原文");
			return content;
		}
	}
	
	/**
	 * 
	 * @Description: 		智能加密，获取正在使用的密钥，以及加密方式，为明文进行加密 
	 * 						若加密失败（密钥不存在等原因），返回明文
	 * @param obj			对象
	 * @param fields 		需要加密的属性
	 * @return 				密文
	 * @author zyt
	 */
	public static void smartEncrypt(Object obj, String[] fields){
		SecurityKeyInfo keyInfo = null;
		try {
			keyInfo = getCurrentDataKeyInfo();
		} catch (Exception e) {
			logger.error("获取当前密钥失败：", e);
		}
		if(keyInfo != null){
			if(keyInfo.getEncryptType() == EncryptType.THREE_DES_THREE_KEYS){
				try {
					encrypt3DES3KEYS(obj, keyInfo, fields);
				} catch (Exception e) {
					handleException(e);
					logger.error("对对象加密失败，原因：", e);
				}
			}
		} else {
			logger.error("data key为空，加密失败，返回原文");
		}
	}
	

	/**
	 * 
	 * @Description: 	对对象的某些属性进行3DES加密，密钥为3KEY长度 ，若传入属性为空，则使用默认的属性加密
	 * @param obj		对象
	 * @param keyInfo	当前密钥信息
	 * @param fields 	需要加密的属性
	 * @author 			zyt	
	 * @throws Exception 
	 */
	private static void encrypt3DES3KEYS(Object obj, SecurityKeyInfo keyInfo, String[] fields) throws Exception {
		if (obj != null) {

			if (fields == null || fields.length == 0) {
				fields = SensitiveInfoConstants.DEFAULT_FIELDS;
			}
			for (int i = 0; i < fields.length; i++) {
				Field field = ReflectionUtils.findField(obj.getClass(), fields[i]);

				if (field != null) {
					field.setAccessible(true);
					Object val = field.get(obj);
					// 加密数据
					if (val == null) {
						continue;
					}
					val = CipherAlgorithmContext.encryptByKeyInfo(val.toString(), keyInfo);
					ReflectionUtils.setField(field, obj, val);

				}
			}
		}
	};

	/**
	 * 
	 * @Description: 		对字符串密文智能解密
	 * 						根据密文前缀，判断何种加密方式 
	 * @param ciphertext	密文
	 * @return 				明文，若未识别加密方式，则返回原文
	 * @author 				zyt
	 */
	public static String smartDecrypt(String ciphertext) {
		if(StringUtils.isBlank(ciphertext)){
			return ciphertext;
		}
		EncryptType encrypt = CipherAlgorithmContext.getEncryptType(ciphertext);
		if(encrypt == null){
			return ciphertext;
		}
		
		if(encrypt == EncryptType.THREE_DES_THREE_KEYS){
			//3des 3key加密的前缀
			String encryptVersion = getKeyVersionByCipherText(ciphertext, encrypt);
			SecurityKeyInfo keyInfo = getDataKeyInfo(encrypt, encryptVersion);
			if(keyInfo == null || StringUtils.isBlank(keyInfo.getKeyContent())){
				logger.error("未找到对应的密钥，解密失败，返回原文");
				return ciphertext;
			}
			try {
				return CipherAlgorithmContext.decryptByKeyInfo(ciphertext, keyInfo);
			} catch (Exception e) {
				handleException(e);
				logger.error("智能解密失败，返回原文，原因：", e);
				return ciphertext;
			}
		} else {
			try {
				return CipherAlgorithmContext.decryptByKeyInfo(ciphertext, null);
			} catch (Exception e) {
				handleException(e);
				logger.error("智能解密失败，返回原文，原因：" , e);
				return ciphertext;
			}
		}
	};
	/**
	 * 
	 * @Description: 	对对象的某些属性智能解密，若传入为空，则使用默认字段对对象加密 
	 * 					根据密文前缀，判断何种加密方式
	 * @param obj		对象
	 * @param fields 	属性
	 * @author 			zyt
	 */
	public static void smartDecrypt(Object obj, String[] fields) {
		if (obj != null) {

			if (fields == null || fields.length == 0) {
				fields = SensitiveInfoConstants.DEFAULT_FIELDS;
			}
			for (int i = 0; i < fields.length; i++) {
				Field field = ReflectionUtils.findField(obj.getClass(), fields[i]);

				if (field != null) {
					try {
						field.setAccessible(true);
						Object val = field.get(obj);
						if (val == null) {
							continue;
						}
						val = smartDecrypt(val.toString());
						ReflectionUtils.setField(field, obj, val);
					} catch (Exception e) {
						handleException(e);
						logger.error("智能解密对象失败，原因：" , e);
					}
				}
			}
		}
	}

	/**
	 * 
	 * @Description: 	获取当前正在使用的密钥信息 
	 * @return 			密钥内容
	 * @author 			zyt
	 */
	private static SecurityKeyInfo getCurrentDataKeyInfo(){
		SecurityKeyInfo keyInfo = getKeyBox().getCurrentKeyInfo();
		return keyInfo;
	};
	
	/**
	 * 
	 * @Description: 			根据加密方式和密钥版本，获取对应的密钥信息 
	 * @param encryptType		加密方式
	 * @param keyVersion		密钥版本
	 * @return 					密钥信息
	 * @author zyt
	 */
	private static SecurityKeyInfo getDataKeyInfo(EncryptType encryptType, String keyVersion){
		String prefix = "";
		if (encryptType == EncryptType.THREE_DES_THREE_KEYS) {
			prefix = SensitiveInfoConstants.ENCRYPT_MARK_3DES_3KEY + keyVersion + "_" + SensitiveInfoConstants.ENCRYPT_COMMON_MARK;
		}
		return getKeyBox().getAllKeyInfos().get(prefix);
	};
	
	/**
	 * 
	 * @Description: 	对一个集合中所有的对象的某些属性智能解密，若传入为空，则使用默认字段对对象加密 
	 * 					根据密文前缀，判断何种加密方式
	 * @param list		对象集合
	 * @param fields 	属性
	 * @author 			zyt
	 */
	public static void smartDecryptList(List list, String[] fields){
		if(list == null || list.size() < 1){
			return;
		}
		for(Object obj : list){
			smartDecrypt(obj, fields);
		}
		
	}


	
	/**
	 * 
	 * @Description: 			根据密文和加密方式，获取密文使用的加密方式 
	 * @param cipherText		密文
	 * @param encryptType		加密方式
	 * @return 					密钥版本
	 * @author zyt
	 */
	private static String getKeyVersionByCipherText(String cipherText, EncryptType encryptType){
		String encryptVersion = "";
		if(encryptType == EncryptType.THREE_DES_THREE_KEYS){
			encryptVersion = cipherText.substring(SensitiveInfoConstants.ENCRYPT_MARK_3DES_3KEY.length(), 
					cipherText.indexOf("_" + SensitiveInfoConstants.ENCRYPT_COMMON_MARK));
		}
		
		return encryptVersion;
	}
	
	/**
	 * 
	 * @Description: 		判断密钥box是否已经初始化 
	 * @param myKeyBox		需要判断的密钥box
	 * @return 				是，true，否：false
	 * @author zyt
	 */
	private static boolean isKeyInit(SecurityInfoKeyBox myKeyBox){
		if(myKeyBox == null || myKeyBox.getCurrentKeyInfo() == null || StringUtils.isBlank(myKeyBox.getCurrentKeyInfo().getKeyContent())){
			return false;
		}
		return true;
	}
	
	/**
	 * 
	 * @Description: 	加解密异常时候的处理 ,是否需要发送短信通知出错？？？？？
	 * @param e 		异常
	 * @author zyt
	 */
	private static void handleException(Exception e){
		SensitiveInfoDictionaryUtils.sendExceptionSms(e);
	}
	
	public static void main(String[] args) throws Exception {
		/*EpCardBindInfo ep = new EpCardBindInfo();
		//ep.setBankAcctNum("ENCRPYT_3DES_3_MARK_pCqrZmQdPC+ZdzpzSCD4fJ/oROBvIlNc");
		ep.setBankAcctNum("M_56352a53bdcc03593032117335e010753976467d481a3aa7");
		ep.setBankCertNo("142325198703180013");
		ep.setBankAcctName("ENCRPYT_3DES_3_MARK_1hfARFK1IEdGVHxHtJXXjA==");
		ep.setBankCode("001");
		SensitiveInfoUtils s = new SensitiveInfoUtils();
		String[] fields = {"bankAcctNum", "bankAcctName"};
		s.smartDecrypt(ep,fields);
		System.out.println(ep);*/
		//SecurityInfoKeyBox keyBox1 = new SecurityInfoKeyBox();
		//System.out.println(keyBox1.getCurrentKeyInfo());
		//String test = "123123";
		//System.out.println(test);
		String content = "211121199408013613";
		try {
			Encrypt encrypt = new Encrypt();
			String desEncryptContent = SensitiveInfoConstants.ENCRYPT_MARK_DES + encrypt.encrypt(content);
			System.out.println("des:" + desEncryptContent);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		byte[] twoDesEncryptByte = 
				Cipher3DES.encryptBase64(SensitiveInfoConstants.key3des2keyBytes, content.getBytes());
		String twoDesEncryptContent = SensitiveInfoConstants.ENCRYPT_MARK_3DES_2KEY + new String(twoDesEncryptByte);
		System.out.println("2des:" + twoDesEncryptContent);
		/*List<String> list = null;
		for(String s :list){
			System.out.println(keyBox1.getCurrentKeyInfo());
		}*/
	}


}
